package com.jd.dlc.limit;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import com.jd.dlc.domain.LimiterRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName: GuavaLimiter
 * @Description:
 * @Author: MengXiangXin
 * @Date: 2022/5/9 10:04 PM
 */
@Slf4j
public class GaLimiter extends AbsLimiter implements InitializingBean {


    private volatile Cache<String, RateLimiter> limiterCache;

    /**
     * @Description:
     * @Date 2022/5/10 11:48 AM
     * @Param [limitKey, timeout, limit]
     * @Return boolean
     **/
    @Override
    public boolean isAllowed(String limitKey, long timeout, long limit) {
        String key = PREFIX_LIMIT + limitKey;
        RateLimiter rateLimiter = limiterCache.getIfPresent(key);
        if (Objects.isNull(rateLimiter)) {
            RateLimiter ifPresent = limiterCache.getIfPresent(key);

            /**
             * double check
             */
            if (Objects.nonNull(ifPresent)) {
                return ifPresent.tryAcquire();
            }
            rateLimiter = RateLimiter.create(limit);
            limiterCache.put(key, rateLimiter);
        }

        return rateLimiter.tryAcquire(timeout, TimeUnit.MICROSECONDS);
    }

    @Override
    public void afterPropertiesSet() {
        limiterCache = building();
    }

    private Cache<String, RateLimiter> building() {
        return CacheBuilder.newBuilder().maximumSize(dlcProperties.getLimiter().getGaKeySize())
                .expireAfterWrite(dlcProperties.getLimiter().getGaKeyTimeout()
                        , TimeUnit.SECONDS).build();
    }

    @Override
    boolean doRefreshLimiter(LimiterRule limiterRule) {
        try {
            limiterCache = building();
            log.error("limiter reloading success");
        } catch (Exception ex) {
            log.error("limiter reloading error{}", ex);
            return false;
        }
        return true;
    }
}
